#!/usr/bin/env ruby

# bogus chart that we create just so there is at least one chart
CHART_TYPE = 'lines'
UPDATE_EVERY = 1
PRIORITY = 100000
CHART_NAME = 'number_of_processes'
DIMENSION_NAME = 'running'

$plugin_name = "external_plugin"
$plugin_version = "0.0.1"
$plugin_config = <<-HEREDOC
test_plugin_config
hableba hableba hableba
HEREDOC

$array_module_name = 'module_of_the_future'
$fixed_job_name = 'fixed_job'

$modules = {
  $array_module_name => {
    :type => :job_array,
    :jobs => {
      $fixed_job_name => {
        :type => :fixed,
        :config => <<-HEREDOC
fixed_job_config
HEREDOC
      },
    },
    :config => <<-HEREDOC
module_of_the_future_config
HEREDOC
  },
  "module_of_the_future_single_type" => {
    :type => :single,
    :jobs => {},
    :config => <<-HEREDOC
module_of_the_future_single_type_config
HEREDOC
  }
}

def out(str)
  $log.puts "2 NETDATA> #{str}"
  $stdout.puts str
  $stdout.flush
  $log.flush
end

def log(str)
  $log.puts "LOG      > #{str}"
  $log.flush
end

#TODO this is AI code, verify
def split_with_quotes(str)
  result = []
  current_word = ""
  in_quotes = false
  escaped = false

  str.each_char do |char|
    if char == '\\' && !escaped
      escaped = true
      next
    end

    if char == '"' && !escaped
      in_quotes = !in_quotes
      current_word << char
    elsif char == ' ' && !in_quotes
      result << current_word unless current_word.empty?
      current_word = ""
    else
      current_word << char
    end

    escaped = false
  end

  result << current_word unless current_word.empty?

  result
end


def print_startup_messages
  out "DYNCFG_ENABLE #{$plugin_name}"
  $modules.each do |name, module_config|
    out "DYNCFG_REGISTER_MODULE #{name} #{module_config[:type]}"
  end
  out "CHART system.#{CHART_NAME} '' 'Number of running processes' 'processes' processes processes.#{CHART_NAME} #{CHART_TYPE} #{PRIORITY} #{UPDATE_EVERY}"
  out "DIMENSION #{DIMENSION_NAME} '' absolute 1 1"

  $modules.each do |mod_name, mod|
    next unless mod[:type] == :job_array
    mod[:jobs].each do |job_name, job|
      next unless job[:type] == :fixed
      out "DYNCFG_REGISTER_JOB #{mod_name} #{job_name} stock 0"
      out "REPORT_JOB_STATUS #{$array_module_name} #{$fixed_job_name} running 0"
    end
  end
end

def function_result(txid, msg, result)
  out "FUNCTION_RESULT_BEGIN #{txid} #{result} text/plain 5"
  out msg
  out "FUNCTION_RESULT_END"
end

def process_payload_function(params)
  log "payload function #{params[:fncname]}, #{params[:fncparams]}"
  fnc_name, mod_name, job_name = params[:fncparams]
  case fnc_name
  when 'set_plugin_config'
    $plugin_config = params[:payload]
    function_result(params[:txid], "plugin config set", 1)
  when 'set_module_config'
    mod = $modules[mod_name]
    return function_result(params[:txid], "no such module", 0) if mod.nil?
    mod[:config] = params[:payload]
    function_result(params[:txid], "module config set", 1)
  when 'set_job_config'
    mod = $modules[mod_name]
    return function_result(params[:txid], "no such module", 0) if mod.nil?
    job = mod[:jobs][job_name]
    if job.nil?
      job = Hash.new if job.nil?
      job[:type] = :dynamic
      mod[:jobs][job_name] = job
    end
    job[:config] = params[:payload]
    function_result(params[:txid], "job config set", 1)
  end
end

def process_function(params)
  log "normal function #{params[:fncname]}, #{params[:fncparams]}"
  fnc_name, mod_name, job_name = params[:fncparams]
  case fnc_name
  when 'get_plugin_config'
    function_result(params[:txid], $plugin_config, 1)
  when 'get_module_config'
    return function_result(params[:txid], "no such module", 0) unless $modules.has_key?(mod_name)
    function_result(params[:txid], $modules[mod_name][:config], 1)
  when 'get_job_config'
    mod = $modules[mod_name]
    return function_result(params[:txid], "no such module", 0) if mod.nil?
    job = mod[:jobs][job_name]
    return function_result(params[:txid], "no such job", 0) if job.nil?
    function_result(params[:txid], job[:config], 1)
  when 'delete_job'
    mod = $modules[mod_name]
    return function_result(params[:txid], "no such module", 0) if mod.nil?
    job = mod[:jobs][job_name]
    return function_result(params[:txid], "no such job", 0) if job.nil?
    if job[:type] == :fixed
      return function_result(params[:txid], "this job can't be deleted", 0) 
    else
      mod[:jobs].delete(job_name)
      function_result(params[:txid], "job deleted", 1)
    end
  end
end

$inflight_incoming = nil
def process_input(input)
  words = split_with_quotes(input)

  unless $inflight_incoming.nil?
    if input == "FUNCTION_PAYLOAD_END"
      log $inflight_incoming[:payload]
      process_payload_function($inflight_incoming)
      $inflight_incoming = nil
    else
      $inflight_incoming[:payload] << input
      $inflight_incoming[:payload] << "\n"
    end
    return
  end

  case words[0]
  when "FUNCTION", "FUNCTION_PAYLOAD"
    params = {}
    params[:command] = words[0]
    params[:txid] = words[1]
    params[:timeout] = words[2].to_i
    params[:fncname] = words[3]
    params[:fncname] = params[:fncname][1..-2] if params[:fncname].start_with?('"') && params[:fncname].end_with?('"')
    if params[:command] == "FUNCTION_PAYLOAD"
      $inflight_incoming = Hash.new
      params[:fncparams] = split_with_quotes(params[:fncname])
      params[:fncname] = params[:fncparams][0]
      $inflight_incoming[:txid] = params[:txid]
      $inflight_incoming[:fncname] = params[:fncname]
      $inflight_incoming[:params] = params
      $inflight_incoming[:fncparams] = params[:fncparams]
      $inflight_incoming[:payload] = ""
    else
      params[:fncparams] = split_with_quotes(params[:fncname])
      params[:fncname] = params[:fncparams][0]
      process_function(params)
    end
  end
end

def read_and_output_metric
  processes = `ps -e | wc -l`.to_i - 1 # -1 to exclude the header line
  timestamp = Time.now.to_i

  puts "BEGIN system.#{CHART_NAME}"
  puts "SET #{DIMENSION_NAME} = #{processes}"
  puts "END"
end

def the_main
  $stderr.reopen("/tmp/test_plugin_err.log", "w")
  $log = File.open("/tmp/test_plugin.log", "w")
  $log.puts "Starting plugin"
  print_startup_messages
  $log.puts "init done"
  $log.flush

  last_metric_time = Time.now

  loop do
    time_since_last_metric = Time.now - last_metric_time

    # If it's been more than 1 second since we collected metrics, collect them now
    if time_since_last_metric >= 1
      read_and_output_metric
      last_metric_time = Time.now
    end

    # Use select to wait for input, but only wait up to the time remaining until we need to collect metrics again
    remaining_time = [1 - time_since_last_metric, 0].max
    if select([$stdin], nil, nil, remaining_time)
      input = $stdin.gets
      next if input.class != String
      input.chomp!
      $log.puts "RAW INPUT< #{input}"
      $log.flush
      process_input(input)
    end
  end
end


the_main if __FILE__ == $PROGRAM_NAME
